﻿#region License
// Copyright 2010 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using MediaWiki.Common;
using MediaWiki.MediaWikiConverter.Utilities.Logging.Scope;
using MediaWiki.MediaWikiConverter.Utilities.Scope;

namespace MediaWiki.MediaWikiConverter.Utilities.Logging
{
  public static class Logger
  {
    private static IScope<LogContext> _scope;
    private readonly static IList<LogInstance> _loggers;

    public static bool SuppressErrors { get; set; }

    public static IScope<LogContext> LogContextScope
    {
      get { return _scope; }
      set
      {
        ValidationUtils.ArgumentNotNull(value, "value");
        _scope = value;
      }
    }

    public static LogContext Context
    {
      get { return _scope.Current; }
    }

    public static IList<LogInstance> Loggers
    {
      get { return _loggers; }
    }

    static Logger()
    {
      _scope = new ThreadLogScope();
      _loggers = new List<LogInstance>();
      SuppressErrors = true;
    }

    public static void Trace(string message, Action a)
    {
      Trace(message, null, true, a);
    }

    public static void Trace(string message, string category, Action a)
    {
      Trace(message, category, true, a);
    }

    public static void Trace(string message, string category, bool indent, Action a)
    {
      if (a == null)
        throw new ArgumentNullException("a");

      Func<object> f = () => { a(); return null; };

      Trace(message, category, f);
    }

    public static T Trace<T>(string message, Func<T> f)
    {
      return Trace(message, null, true, f);
    }

    public static T Trace<T>(string message, string category, Func<T> f)
    {
      return Trace(message, category, true, f);
    }

    public static T Trace<T>(string message, string category, bool indent, Func<T> f)
    {
      if (f == null)
        throw new ArgumentNullException("f");

      LogTrace trace = new LogTrace(message, category, indent);

      try
      {
        trace.Start();
        return f();
      }
      catch (Exception ex)
      {
        trace.Error(ex);

        throw;
      }
      finally
      {
        trace.Complete();
      }
    }

    private static void LogInternal(LogLevel logLevel, string message, params string[] categories)
    {
      LogInternal(new LogEntry { Message = message, Categories = categories, LogLevel = logLevel, TimeStamp = DateTimeOffset.UtcNow });
    }

    public static void Verbose(string message, params string[] categories)
    {
      LogInternal(LogLevel.Verbose, message, categories);
    }

    public static void Info(string message, params string[] categories)
    {
      LogInternal(LogLevel.Information, message, categories);
    }

    public static void Warn(string message, params string[] categories)
    {
      LogInternal(LogLevel.Warning, message, categories);
    }

    public static void Error(string message, params string[] categories)
    {
      LogInternal(LogLevel.Error, message, categories);
    }

    public static void Critical(string message, params string[] categories)
    {
      LogInternal(LogLevel.Critical, message, categories);
    }

    public static void CriticalException(string message, Exception exception, params string[] categories)
    {
      LogExceptionInternal(LogLevel.Critical, message, exception, categories);
    }

    public static void ErrorException(string message, Exception exception, params string[] categories)
    {
      LogExceptionInternal(LogLevel.Error, message, exception, categories);
    }

    public static void LogException(LogLevel logLevel, string message, Exception exception, params string[] categories)
    {
      LogExceptionInternal(logLevel, message, exception, categories);
    }

    private static void LogExceptionInternal(LogLevel logLevel, string message, Exception exception, ICollection<string> categories)
    {
      StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
      TextExceptionFormatter exceptionFormatter = new TextExceptionFormatter(sw, exception);
      exceptionFormatter.Format();

      string exceptionDetails = sw.ToString();

      if (!string.IsNullOrEmpty(message))
        exceptionDetails = message + Environment.NewLine + exceptionDetails;

      LogInternal(new LogEntry(exceptionDetails, categories, logLevel, "Error"));
    }

    public static void Log(LogEntry entry)
    {
      ValidationUtils.ArgumentNotNull(entry, "entry");

      LogInternal(entry);
    }

    private static void LogInternal(LogEntry entry)
    {
      foreach (LogInstance logger in _loggers)
      {
        try
        {
          logger.Log(entry);
        }
        catch
        {
          if (!SuppressErrors)
            throw;
        }
      }
    }

    public static void Indent()
    {
      Context.Indent();
    }

    public static void Unindent()
    {
      Context.Unindent();
    }

    public static int IndentSize
    {
      get { return Context.IndentSize; }
    }
  }
}